﻿using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;

namespace Job.Core.Security
{
    /// <summary>
    /// 基于AES对称算法加密解密封装(无法继承此类)。
    /// </summary>
    public sealed class Aes
    {
        #region Properties

        private AesManaged aes;       

        private string password;

        /// <summary>
        /// 密钥密码。
        /// </summary>
        public string Password
        {
            get { return password; }
            set
            {
                if (string.IsNullOrEmpty(value))
                    throw new ArgumentNullException("Password");

                password = value;
            }
        }

        private string salt;

        /// <summary>
        /// 密钥 Salt。
        /// </summary>
        public string Salt
        {
            get { return salt; }
            set
            {
                if (value == null || value.Length < 8)
                    throw new ArgumentException("'Salt' 不能小于8位。");

                salt = value;
            }
        }       
        #endregion

        #region Constructor

        /// <summary>
        /// 初始化一个 Aes 类的新实例。
        /// </summary>
        /// <param name="password">密钥密码。</param>
        /// <param name="salt">密钥 Salt。</param>
        public Aes(string password, string salt)
        {
            Password = password;
            Salt = salt;

            aes = new AesManaged();           
            byte[] st = Encoding.UTF8.GetBytes(Salt);

            // Rfc2898DeriveBytes - 通过使用基于 HMACSHA1 的伪随机数生成器，实现基于密码的密钥派生功能 (PBKDF2 - 一种基于密码的密钥派生函数)。
            // 通过 密码 和 salt 派生密钥。
            Rfc2898DeriveBytes rfc = new Rfc2898DeriveBytes(Password, st);

            /*
             * AesManaged.BlockSize - 加密操作的块大小（单位：bit）。
             * AesManaged.LegalBlockSizes - 对称算法支持的块大小（单位：bit）。
             * AesManaged.KeySize - 对称算法的密钥大小（单位：bit）。
             * AesManaged.LegalKeySizes - 对称算法支持的密钥大小（单位：bit）。
             * AesManaged.Key - 对称算法的密钥。
             * AesManaged.IV - 对称算法的密钥大小。
             * Rfc2898DeriveBytes.GetBytes(int 需要生成的伪随机密钥字节数) - 生成密钥。
             */
            aes.BlockSize = aes.LegalBlockSizes[0].MaxSize;
            aes.KeySize = aes.LegalKeySizes[0].MaxSize;
            aes.Key = rfc.GetBytes(aes.KeySize / 8);
            aes.IV = rfc.GetBytes(aes.BlockSize / 8);
        }    
   
        #endregion

        #region Methods

        /// <summary>
        /// 加密数据。
        /// </summary>
        /// <param name="data">需加密的数据。</param>
        /// <returns>加密后的数据。</returns>
        public string Encrypt(string data)
        {
            if (data == null)
                throw new ArgumentNullException("data");

            byte[] bt = Encoding.UTF8.GetBytes(data);

            // 用当前的 Key 属性和初始化向量 IV 创建对称加密器对象。
            ICryptoTransform encryptTransform = aes.CreateEncryptor();

            // 加密后的输出流
            MemoryStream encryptStream = new MemoryStream();

            // 将加密后的目标流（encryptStream）与加密转换（encryptTransform）相连接。
            CryptoStream encryptor = new CryptoStream(encryptStream, encryptTransform, CryptoStreamMode.Write);

            // 将一个字节序列写入当前 CryptoStream （完成加密的过程）。
            encryptor.Write(bt, 0, bt.Length);
            encryptor.Close();

            // 将加密后所得到的流转换成字节数组，再用Base64编码将其转换为字符串。
            return Convert.ToBase64String(encryptStream.ToArray()).Replace('+', '!').Replace('/', '-'); //将+改成!，防止JavaScript转义为空格，/转义成-，防止Url中目录分格。           
        }

        /// <summary>
        /// 解密数据。
        /// </summary>
        /// <param name="data">加密数据。</param>
        /// <returns>解密后的数据。</returns>
        public string Decrypt(string data)
        {
            if (data == null)
                throw new ArgumentNullException("data");

            data = data.Replace('!', '+').Replace('-', '/'); //加密时转变的特殊字符还原。
            byte[] bt = Convert.FromBase64String(data);

            // 用当前的 Key 属性和初始化向量 IV 创建对称解密器对象。
            ICryptoTransform decryptTransform = aes.CreateDecryptor();

            // 解密后的输出流。
            MemoryStream decryptStream = new MemoryStream();

            // 将解密后的目标流（decryptStream）与解密转换（decryptTransform）相连接。
            CryptoStream decryptor = new CryptoStream(decryptStream, decryptTransform, CryptoStreamMode.Write);

            // 将一个字节序列写入当前 CryptoStream （完成解密的过程）。
            decryptor.Write(bt, 0, bt.Length);
            decryptor.Close();

            // 将解密后所得到的流转换为字符串。
            byte[] decryptBytes = decryptStream.ToArray();
            return Encoding.UTF8.GetString(decryptBytes, 0, decryptBytes.Length);
        }    
          
        #endregion
    }
}